﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Diagnostics;
using System.Collections;
using System.ComponentModel;

namespace tools_library
{
    public class NetworkTool
    {
        public string ServerName; //name of this server
        public int PortNo;
        public Hashtable Connections = new Hashtable(); //all clients (ip-end points) associated with this server
        private bool IsRunning = false; //determines whether server is running or not
        public delegate void MessageReceivedDelegate(object sender, MessageReceivedArgs e);
        public MessageReceivedDelegate MessageReceivedCallback;

        private Socket TcpServerSocket;
        private Thread ListenTcpThread;
        private ISynchronizeInvoke Sync;

        public Node GetLocalNode()
        {
            string hostName = Dns.GetHostEntry("localhost").HostName;
            Node retval = new Node(new IPEndPoint(IPAddress.Loopback,PortNo));
            return retval;
        }

        /// <summary>
        /// Broad-casts the message to all the remote-nodes.
        /// </summary>
        /// <param name="command"></param>
        /// <param name="body"></param>
        public void BroadcastMessage(string command, string body)
        {
            foreach (DictionaryEntry obj in Connections)
            {
                Node node = obj.Value as Node;
                SendMessage(node, command, body);
            }
        }

        /// <summary>
        /// Sends a Tcp message to a remote computer
        /// </summary>
        /// <param name="remoteNode"></param>
        /// <param name="command"></param>
        /// <param name="body"></param>
        public void SendMessage(Node remoteNode, string command, string body)
        {
            string remoteHost = Dns.GetHostEntry(remoteNode.EndPoint.Address).HostName;
            int remotePort = remoteNode.EndPoint.Port;
            SendMessage(remoteHost, remotePort, command, body);
        }

        /// <summary>
        /// Sends a Tcp message to a remote host
        /// </summary>
        /// <param name="remoteHost"></param>
        /// <param name="remotePort"></param>
        /// <param name="command"></param>
        /// <param name="body"></param>
        public void SendMessage(string remoteHost, int remotePort, string command, string body)
        {
            try
            {
                //string hostName = Dns.GetHostEntry(remoteClient.EndPoint.Address).HostName;
                string localHostName = Dns.GetHostEntry("localhost").HostName;
                TcpClient client = new TcpClient(remoteHost,remotePort);
                //TcpClient client = new TcpClient();
                Stream s = client.GetStream();
                StreamWriter sw = new StreamWriter(s);
                sw.AutoFlush = true;
                sw.Write(localHostName + "#" + PortNo.ToString() + "#" + ServerName + "#" + command + "#" + body);
                sw.Close();
                s.Close();
                client.Close();
            }
            catch 
            { 
            }
        }

        /// <summary>
        /// Starts the server on the specified port
        /// </summary>
        /// <param name="portNo">local port number on which to listen</param>
        /// <param name="callBack"></param>
        /// <param name="sync">the gui object to sync with. otherwise, leave it null</param>
        public void StartServer(int portNo, MessageReceivedDelegate callBack, ISynchronizeInvoke sync)
        {
            StartServer("USR_" + Dns.GetHostEntry("localhost").HostName, portNo, callBack,sync);
        }


        /// <summary>
        /// Starts the server on the specified port
        /// </summary>
        /// <param name="serverName">a name representing the program or user on this node</param>
        /// <param name="portNo"></param>
        /// <param name="callBack"></param>
        /// <param name="sync"></param>
        public void StartServer(string serverName, int portNo, MessageReceivedDelegate callBack, ISynchronizeInvoke sync)
        {
            try
            {
                this.ServerName = serverName;
                this.PortNo = portNo;
                this.MessageReceivedCallback = callBack;
                this.Sync = sync;
                //this.Sync = (ISynchronizeInvoke)this;

                //start the tcp thread
                IsRunning = true;
                ThreadStart ts = new ThreadStart(ListenTcpRequests);
                ListenTcpThread = new Thread(ts);
                ListenTcpThread.IsBackground = true;
                ListenTcpThread.Start();


                //broadcast to all servers in the network
                //that i'm listening on this port
                System.Threading.Thread.Sleep(250);
                //Broadcast();
            }
            catch
            {
                //Debug.Print(ex.Message);
            }
        }

        /// <summary>
        /// Stops the server
        /// </summary>
        public void StopServer()
        {
            try
            {
                //ListenTcpThread.Abort();
                IsRunning = false;
                while (ListenTcpThread.ThreadState == System.Threading.ThreadState.Running)
                { }
                TcpServerSocket.Close();
                //Udpc.Close();
            }
            catch { }
        }

        /// <summary>
        /// Listens to new TcpRequests and processes them on its own thread
        /// </summary>
        private void ListenTcpRequests()
        {
            try
            {
                TcpServerSocket = new Socket(IPAddress.Any.AddressFamily, SocketType.Stream, ProtocolType.IP);
                IPEndPoint ep = new IPEndPoint(IPAddress.Any, PortNo);
                TcpServerSocket.Bind(ep);
                TcpServerSocket.Listen(10);
                while (IsRunning)
                {
                    Socket soc = TcpServerSocket.Accept(); //Listener.AcceptSocket(); //blocks until a tcp connection request is received
                    Stream ns = new NetworkStream(soc);
                    StreamReader sr = new StreamReader(ns);
                    string message = sr.ReadToEnd();
                    //Debug.WriteLine(message);

                    //decode the entire message
                    //MACHINE#PORT#SENDER_NAME#COMMAND#BODY
                    string machineName =message.Split("#".ToCharArray())[0];
                    int portNo; 
                    int.TryParse(message.Split("#".ToCharArray())[1],out portNo);
                    string sender = message.Split("#".ToCharArray())[2];
                    string command = message.Split("#".ToCharArray())[3];
                    string body = message.Split("#".ToCharArray())[4];
                    switch (command)
                    {
                        case "$message":
                            //SendTcpMessage((soc.RemoteEndPoint as IPEndPoint ), "RECEIPT", "");
                            break;
                        case "$file":
                            break;
                        case "$stopcontrol":
                            break;
                    }
                    //parse the client
                    Node rnode=null;
                    IPEndPoint remoteEp = new IPEndPoint(
                        (soc.RemoteEndPoint as IPEndPoint).Address, portNo);
                    if (!Connections.Contains(remoteEp.Address.ToString() + ":" + portNo.ToString()))
                    {
                        rnode = new Node(remoteEp);
                        //Connections.Add(sender + ":" + portNo.ToString(), client);
                        Connections.Add(remoteEp.Address.ToString() + ":" + portNo.ToString(), rnode);
                    }

                    //raise the event
                    MessageReceivedArgs e = new MessageReceivedArgs();
                    e.RemoteNode = Connections[remoteEp.Address.ToString() + ":" + portNo.ToString()] as Node; //client;
                    //e.Sender = sender;
                    e.Command = command;
                    e.Body = body;
                    //MessageReceived(this, e);
                    //
                    object[] items = new object[2];
                    items[0] = this;
                    items[1] = e;

                    if (command == "$disconnect")
                    {
                        Connections.Remove(sender + ":" + portNo.ToString());
                    }
                    else
                    {
                        if (Sync == null)
                        {
                            MessageReceivedCallback.Invoke(this, e);
                        }
                        else
                        {
                            Sync.Invoke(MessageReceivedCallback, items);
                        }
                    }

                    //clean up
                    sr.Close();
                    ns.Close();
                    soc.Close();
                }
                BroadcastMessage("$disconnect", "");
            }
            catch 
            {
                //Debug.WriteLine("Error occured: " + ex.Message);
            }
        }

        public bool IsToolRunning 
        { 
            get {
                return IsRunning;
            }
        }
    }

    public class MessageReceivedArgs : EventArgs
    {
        public string Command;
        public string Body;
        public Node RemoteNode;
    }

    public class Node
    {
        public Hashtable Attributes = new Hashtable();
        public IPEndPoint EndPoint;
        public Node(IPEndPoint ep)//(string hostName, int portNo)
        {
            this.EndPoint = ep;
        }

        public string GetHostName()
        {
            return Dns.GetHostEntry(EndPoint.Address).HostName;
        }

        public override string ToString()
        {
            //return Dns.GetHostEntry(EndPoint.Address).HostName + ":" +
                //EndPoint.Port.ToString();
            return EndPoint.Address.ToString() + ":" +
                EndPoint.Port.ToString();
        }
        //public string HostName;
        //public int PortNo;
    }
}
